﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using TfsWorkItemTracking = Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using FunctionPoint.Services;
using Common = FunctionPoint.Common;
using FunctionPoint;

namespace FunctionPoint.Utility
{
    public class TfsUtility
    {

        private ITfsService _tfsService = null;

        public TfsUtility(ITfsService tfsService)
        {
            _tfsService = tfsService;
        }

        //public static TfsUtility GetDefaultInstance()
        //{
        //    return new TfsUtility(new LocalTfsService());
        //}

        public ITfsService TfsService
        {
            get
            {
                return _tfsService;
            }
        }
        public ObservableCollection<Common.WorkItemQueryFolder> GetAllQueriesAndOptionallyWorkItems(string projectName, bool includeWorkItems = false, bool flattenHierarchy = false)
        {
            var project = _tfsService.WorkItemStore.Projects[projectName];
            ObservableCollection<Common.WorkItemQueryFolder> workItemQueryFolderCollection = new ObservableCollection<Common.WorkItemQueryFolder>();
            Common.ObservableFlattenedQueryHierarchy flatQueries = new Common.ObservableFlattenedQueryHierarchy();
            project.QueryHierarchy.ToList().ForEach(o =>
            {
                BuildQueryTree(o, null, workItemQueryFolderCollection, flatQueries, flattenHierarchy, 0, includeWorkItems);
            });

            return workItemQueryFolderCollection;
        }

        public Common.ObservableFlattenedQueryHierarchy GetFlattenedQueryHierarchy(string projectName, bool includeWorkItems = false)
        {
            var project = _tfsService.WorkItemStore.Projects[projectName];
            ObservableCollection<Common.WorkItemQueryFolder> workItemQueryFolderCollection = new ObservableCollection<Common.WorkItemQueryFolder>();
            Common.ObservableFlattenedQueryHierarchy flatQueries = new Common.ObservableFlattenedQueryHierarchy();
            project.QueryHierarchy.ToList().ForEach(o =>
            {
                BuildQueryTree(o, null, workItemQueryFolderCollection, flatQueries, true, 0, includeWorkItems);
            });


            return flatQueries;
        }

        public void BuildQueryTree(QueryItem queryFolder, Common.WorkItemQueryFolder parentWorkItemQueryFolder, ObservableCollection<Common.WorkItemQueryFolder> workItemQueryFolderCollection, Common.ObservableFlattenedQueryHierarchy flattenedQueryItemCollection, bool flattenHierarchy, int nestLevel, bool populateWorkItems = false)
        {
            if (queryFolder.GetType() == typeof(QueryFolder))
            {
                var folder = (QueryFolder)queryFolder;
                Common.WorkItemQueryFolder wiQueryFolder = new Common.WorkItemQueryFolder()
                {
                    Name = folder.Name,
                    ItemPath = folder.Path,
                    Id = folder.Id,
                    NestLevel = nestLevel,
                };

                if (parentWorkItemQueryFolder != null)
                    parentWorkItemQueryFolder.Folders.Add(wiQueryFolder);
                else
                    workItemQueryFolderCollection.Add(wiQueryFolder);

                flattenedQueryItemCollection.Add(wiQueryFolder);

                folder.ToList().ForEach(folderItem =>
                {
                    BuildQueryTree(folderItem, wiQueryFolder, workItemQueryFolderCollection, flattenedQueryItemCollection, flattenHierarchy, nestLevel + 1, populateWorkItems);
                });
            }
            else
            {
                // at a query, find the parent in the tree...

                // var parentFolder = workItemQueryFolderCollection.FirstOrDefault(o => o.Id == queryFolder.Parent.Id);

                Common.WorkItemQuery query = new Common.WorkItemQuery()
                {
                    ItemPath = queryFolder.Path,
                    Name = queryFolder.Name,
                    ParentFolder = parentWorkItemQueryFolder,
                    QueryId = queryFolder.Id,
                    NestLevel = nestLevel,
                };

                parentWorkItemQueryFolder.Queries.Add(query);

                flattenedQueryItemCollection.Add(query);

                if (populateWorkItems)
                    GetQueryWorkItems(query);


            }
        }

        public void GetQueryWorkItems(Common.WorkItemQuery queryItem)
        {

            TfsWorkItemTracking.QueryDefinition queryDef = _tfsService.WorkItemStore.GetQueryDefinition(queryItem.QueryId);
            Dictionary<string, object> propertyBag = new Dictionary<string, object>(); // need to put user id or project name?

            propertyBag.Add("project", queryDef.Project.Name);

            #region  if (queryDef.QueryType == TfsWorkItemTracking.QueryType.Tree)

            if (queryDef.QueryType == TfsWorkItemTracking.QueryType.Tree)
            {
                TfsWorkItemTracking.Query query = new TfsWorkItemTracking.Query(_tfsService.WorkItemStore, queryDef.QueryText, propertyBag);

                var queryResults = query.RunLinkQuery().ToList();

                TfsWorkItemTracking.BatchReadParameterCollection batchParameters = new TfsWorkItemTracking.BatchReadParameterCollection();

                foreach (var tfsWorkItem in queryResults)
                {
                    if (tfsWorkItem.SourceId > 0 && !batchParameters.Contains(tfsWorkItem.SourceId))
                        batchParameters.Add(
                            new TfsWorkItemTracking.BatchReadParameter(tfsWorkItem.SourceId));
                    if (tfsWorkItem.TargetId > 0 && !batchParameters.Contains(tfsWorkItem.TargetId))
                        batchParameters.Add(
                            new TfsWorkItemTracking.BatchReadParameter(tfsWorkItem.TargetId));
                }

                if (batchParameters.Count > 0)
                {
                    TfsWorkItemTracking.WorkItemCollection workItemBatch =
                        _tfsService.WorkItemStore.Query(batchParameters, "select * from WorkItems");

                    ConvertWorkItems(queryItem, workItemBatch);

                }

            }



            #endregion


            else if (queryDef.QueryType == TfsWorkItemTracking.QueryType.List)
            {
                TfsWorkItemTracking.Query query = new TfsWorkItemTracking.Query(_tfsService.WorkItemStore, queryDef.QueryText, propertyBag);
                TfsWorkItemTracking.WorkItemCollection tfsWorkItems = query.RunQuery();

                ConvertWorkItems(queryItem, tfsWorkItems);


            }
        }



        public WorkItem CreateDesignPointWorkItem(string designPointTitle, string designComments, string designerName, int parentWorkItemId, string itemPath, int changeSet,
            int lineNumber, int closestCharacterPosition)
        {
            WorkItem designPointWorkItem = null;
            WorkItem parentWorkItem = _tfsService.WorkItemStore.GetWorkItem(parentWorkItemId);


            if (parentWorkItem != null)
            {
                var tfsProject = parentWorkItem.Project;
                var workItemType = GetDesignPointWorkItemType(tfsProject);
                designPointWorkItem = new WorkItem(workItemType)


                    {
                        Title = designPointTitle,
                        Description = designComments,
                        AreaId = parentWorkItem.AreaId,
                        AreaPath = parentWorkItem.AreaPath,
                        IterationId = parentWorkItem.IterationId
                    };


                designPointWorkItem.Fields[CoreField.AssignedTo].Value = designerName;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_CHANGESETID].Value = changeSet;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_CODEELEMENT_CLASS].Value = string.Empty;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_CODEELEMENT_CLOSESTCHARACTERPOSITION].Value = closestCharacterPosition;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_CODEELEMENT_INTERFACE].Value = string.Empty;
                //designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_CODEELEMENT_METHOD].Value = string.Empty;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_CODEELEMENT_NAMESPACE].Value = string.Empty;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_DESIGNERNAME].Value = designerName;
                designPointWorkItem.Fields[Common.Constants.TFS_FIELD_DESIGNPOINT_FILEPATH].Value = itemPath;


                // link type creation.  if we are still on TFS 2008 we can only do related.  Not hierarchial
                WorkItemLinkType genericWorkItemType;
                if (_tfsService.WorkItemStore.WorkItemLinkTypes.Contains(Common.Constants.TFS_LINKTYPE_HIERARCHY))
                {
                    genericWorkItemType = _tfsService.WorkItemStore.WorkItemLinkTypes[Common.Constants.TFS_LINKTYPE_HIERARCHY];

                }
                else
                {
                    // must not support it, have to use the embedded version
                    genericWorkItemType = _tfsService.WorkItemStore.WorkItemLinkTypes[Common.Constants.TFS_LINKTYPE_RELATED];

                }

                var errorList = designPointWorkItem.Validate();

                if (errorList.Count > 0)
                {
                    return null;
                    // throw an exception provbably.  right now.. we're just gonna be stupid
                }
                else
                {

                    designPointWorkItem.Save();

                    // looks backward, and kind of is.  but it's all relative to the source.  Since the parent work item (SourceId) is 
                    WorkItemLink workItemLink = new WorkItemLink(genericWorkItemType.ForwardEnd);
                    workItemLink.SourceId = parentWorkItem.Id;
                    workItemLink.TargetId = designPointWorkItem.Id;

                    designPointWorkItem.WorkItemLinks.Add(workItemLink);

                    designPointWorkItem.Save();
                }



            }


            return designPointWorkItem;
        }





        private WorkItemType GetDesignPointWorkItemType(Project tfsProject)
        {


            foreach (WorkItemType workItemType in tfsProject.WorkItemTypes)
            {
                if (workItemType.Name == FunctionPoint.Common.Constants.TFS_DESIGNPOINTWORKITEMTYPE)
                    return workItemType;
            }

            return null;

        }



        #region Private supporting Methods

      

        private static void ConvertWorkItems(Common.WorkItemQuery owningWorkItemQuery, TfsWorkItemTracking.WorkItemCollection workItemCollection)
        {
            // used for quick searching since it holds reference to the workitem itself.  makes it much easier than traversing the tree
            List<Common.WorkItem> flattenedList = new List<Common.WorkItem>();


            foreach (TfsWorkItemTracking.WorkItem tfsWorkItem in workItemCollection)
            {
                Common.WorkItem workItem = Utilities.CreateCommonWorkItem(tfsWorkItem);

                if (workItem != null)
                {
                    flattenedList.Add(workItem);

                    #region  if (tfsWorkItem.WorkItemLinks.Count > 0)
                    if (tfsWorkItem.WorkItemLinks.Count > 0)
                    {

                        foreach (WorkItemLink workItemLink in tfsWorkItem.WorkItemLinks)
                        {

                            /*
                             * And now for something you'll really like! (but does make sense)
                             * 
                             * WorkItem relationships are defined as Linked Lists.  So when we query for a batch of work items, they are not sorted any particular way 
                             * (nor do they come in true hierarchy form as one may visualize) so you may get a child work item of a parent that has not been iterated
                             * through based on however SQL decided to return the result set.  (Because there can be dependencies vs hierachy forward, reverse which all determine paths.
                             * If you think about it, at some point you would likely return much larger results than desired as well as the possiblity of pulling back every single work item
                             * and just causing hell on the TFS server as it tries to build the object graph)  
                             * 
                             * That aside, we have to look at each work item as it could be one or the other (sorting doesn't seem to work too well because of the level of nesting plus the way 
                             * you have to determine if something is a parent or not.  
                             * 
                             * */


                            if (workItemLink.LinkTypeEnd.ImmutableName == "System.LinkTypes.Hierarchy-Reverse")
                            {
                                // going from the reverse.  guarantees the parent is always there.
                                // create the work item, add to the parent when found

                                var parentWorkItem = flattenedList.FirstOrDefault(o => o.WorkItemId == workItemLink.TargetId);

                                if (parentWorkItem != null) // 
                                {

                                    var owningQueryParentWorkItem = owningWorkItemQuery.WorkItems.FirstOrDefault(o => o.WorkItemId == parentWorkItem.WorkItemId);

                                    if (owningQueryParentWorkItem == null)
                                        owningWorkItemQuery.WorkItems.Add(parentWorkItem);

                                    parentWorkItem.ChildWorkItems.Add(workItem);
                                    workItem.ParentWorkItem = parentWorkItem;

                                }
                                else
                                {
                                    /*
                                     * 
                                     * This is the situation that happens when we get a child work item in the iteration before the parent (depending on the link type)
                                     * 
                                     * so, now we have to find the work item in the parent list.  Create the new item here.  Then add the child workitem.  Forgive me whomever
                                     * has to clean this shit up.  
                                     * 
                                     * */

                                    // alright, find the parent in the TfsWorkItem list...

                                    var workItemList = workItemCollection.Cast<TfsWorkItemTracking.WorkItem>().ToList();

                                    var foundItem = workItemList.FirstOrDefault(o => o.Id == workItemLink.SourceId);

                                    if (foundItem != null)
                                    {
                                        var newParentWorkItem = Utilities.CreateCommonWorkItem(foundItem);
                                        flattenedList.Add(newParentWorkItem);

                                        owningWorkItemQuery.WorkItems.Add(newParentWorkItem);

                                        if (newParentWorkItem.WorkItemId != workItem.WorkItemId)
                                            newParentWorkItem.ChildWorkItems.Add(workItem);
                                    }
                                    // if it's null.  Well.. shit.


                                }
                            }
                            else
                            {

                                var owningQueryParentWorkItem = owningWorkItemQuery.WorkItems.FirstOrDefault(o => o.WorkItemId == workItem.WorkItemId);

                                if (owningQueryParentWorkItem == null)
                                {
                                    // something horribly wrong with my logic here...  
                                    owningWorkItemQuery.WorkItems.Add(workItem);
                                }


                            }
                        }
                    }
                    else
                    {
                        // no links.  Just an item.
                        //    owningWorkItemQuery.WorkItems.Add(workItem);
                        var owningQueryParentWorkItem = owningWorkItemQuery.WorkItems.FirstOrDefault(o => o.WorkItemId == workItem.WorkItemId);

                        if (owningQueryParentWorkItem == null)
                        {
                            // something horribly wrong with my logic here...  

                            owningWorkItemQuery.WorkItems.Add(workItem);
                        }
                    }


                    #endregion
                }
            }
        }
        
        #endregion
    }
}
